package com.founder.devwork.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.founder.devwork.converter.GitAccountConverter;
import com.founder.devwork.converter.GitCommitConverter;
import com.founder.devwork.converter.GitRepositoryConverter;
import com.founder.devwork.entity.Account;
import com.founder.devwork.entity.CommitLog;
import com.founder.devwork.entity.ImportLog;
import com.founder.devwork.entity.Repository;
import com.founder.devwork.persistence.gateway.gitlab.GitGateway;
import com.founder.devwork.persistence.gateway.gitlab.dto.CommitDTO;
import com.founder.devwork.persistence.gateway.gitlab.dto.CommitPkDTO;
import com.founder.devwork.persistence.gateway.gitlab.dto.RepoDTO;
import com.founder.devwork.persistence.gateway.gitlab.dto.UserDTO;
import com.founder.devwork.service.AccountService;
import com.founder.devwork.service.CommitLogService;
import com.founder.devwork.service.GitImportService;
import com.founder.devwork.service.ImportLogService;
import com.founder.devwork.service.RepositoryService;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * GitImportServiceImpl 用于实现 GitImportService 接口。
 * 提供导入GitLab提交记录的方法。
 */
@Slf4j
@Service
@Transactional
public class GitImportServiceImpl implements GitImportService {

    private final Instant ACTIVE_FROM = Instant.parse("2024-07-01T00:00:00Z");

    @Autowired
    @Qualifier("beijingGitGateway")
    private GitGateway beijingGitGateway;

    @Autowired
    @Qualifier("chengduGitGateway")
    private GitGateway chengduGitGateway;

    @Autowired
    private GitRepositoryConverter repoConv;

    @Autowired
    private GitAccountConverter accountConv;

    @Autowired
    private GitCommitConverter commitConv;

    @Autowired
    private RepositoryService repoService;

    @Autowired
    private AccountService accountService;

    @Autowired
    private CommitLogService commitLogService;

    @Autowired
    private ImportLogService importLogService;

    private GitImportService.INSTANCE gitInstance = GitImportService.INSTANCE.BEIJING;

    /**
     * 获取GitGateway实例，根据gitInstance的值，返回对应的GitGateway实例
     *
     * @return GitGateway实例
     */
    private GitGateway gateway() {
        if (gitInstance.equals(INSTANCE.BEIJING)) {
            return beijingGitGateway;
        } else {
            return chengduGitGateway;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void importRepos(INSTANCE instance) {
        log.debug("Importing all repositories in {} start", instance);

        // 1. Fetch all repositories with branches
        List<RepoDTO> repos = getAllReposWithBranches(instance);
        List<Repository> gitapiEntities = repos.stream().map(repoConv::gitRepoDtoToEntity).collect(Collectors.toList());

        // 2. Fetch all repositories from database
        List<Repository> dbEntities = repoService.list();

        // 3. Update repositories
        for (Repository gitapiEntity : gitapiEntities) {
            Repository dbEntity = filterRepoEntityByInstanceAndGitId(dbEntities, instance, gitapiEntity.getGitId());
            if (dbEntity == null) {
                // new repository, fill git instance and updated at
                gitapiEntity.setGitInstance(instance.name().toLowerCase());
                gitapiEntity.setUpdatedAt(LocalDateTime.now());

                repoService.save(gitapiEntity);
            } else {
                // update repository, update last activity at and branch ids and updated at
                dbEntity.setLastActivityAt(gitapiEntity.getLastActivityAt());
                dbEntity.setBranchIds(gitapiEntity.getBranchIds());
                dbEntity.setUpdatedAt(LocalDateTime.now());

                repoService.updateById(dbEntity);
            }
        }

        log.debug("Importing all repositories end");
    }

    /**
     * 过滤出实例和GitLab ID相同的Repository实体
     *
     * @param entities Repository实体列表
     * @param instance 实例
     * @param gitId GitLab ID
     *
     * @return 实例和GitLab ID相同的Repository实体
     */
    Repository filterRepoEntityByInstanceAndGitId(List<Repository> entities, INSTANCE instance, Long gitId) {
        return entities
            .stream()
            .filter(item -> item.getGitInstance().equals(instance.name().toLowerCase()) && item.getGitId().equals(gitId))
            .findFirst()
            .orElse(null);
    }

    /**
     * 获取所有仓库, 包含分支信息
     *
     * <h4>算法：</h4>
     * 从Gitlab API获取所有仓库，并获取每个仓库的分支信息
     *
     * @param instance 实例
     * @return 仓库列表
     */
    private List<RepoDTO> getAllReposWithBranches(INSTANCE instance) {
        this.gitInstance = instance;
        List<RepoDTO> repos = getAllRepos(instance);
        for (int count = 0; count < repos.size(); count++) {
            if (count % 20 == 0) {
                log.info("Fetched {} repos branches", count);
            }
            repos.get(count).setBranches(gateway().getBranches(repos.get(count).getId()));
        }
        return repos;
    }

    /**
     * 获取所有仓库, 但是，不包含分支信息
     *
     * @param instance 实例
     * @return 仓库列表
     */
    private List<RepoDTO> getAllRepos(INSTANCE instance) {
        this.gitInstance = instance;

        Integer page = 1;
        Integer perPage = 50;
        List<RepoDTO> repos = new ArrayList<>();
        while (true) {
            log.debug("Fetched {} repos", repos.size());
            List<RepoDTO> temp = gateway().getAllRepos(page, perPage);
            if (temp.isEmpty()) {
                break;
            } else if (temp.size() < perPage) {
                repos.addAll(temp);
                break;
            } else {
                repos.addAll(temp);
                page++;
            }
        }
        return repos;
    }

    /**
     * 过滤出活跃的仓库
     *
     * @param repos 仓库列表
     * @return 活跃的仓库列表
     */
    private List<RepoDTO> filterActiveRepos(List<RepoDTO> repos) {
        List<RepoDTO> activeRepose = repos
            .stream()
            .filter(item -> item.getLastActivityAt().isAfter(ACTIVE_FROM))
            .collect(Collectors.toList());
        log.info("Fetched {} repos, active repos: {}", repos.size(), activeRepose.size());
        return activeRepose;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void importAccounts(INSTANCE instance) {
        log.debug("Importing all accounts in {} start", instance);

        // 1. Fetch all accounts from gitlab
        List<UserDTO> gitAccounts = fetchAllAccounts(instance);
        List<Account> gitAccountEntities = gitAccounts.stream().map(accountConv::gitAccountDtoToEntity).collect(Collectors.toList());

        // 2. Fetch all accounts from database
        List<Account> dbAccounts = accountService.list();

        // 3. Update accounts
        for (Account gitAccountEntity : gitAccountEntities) {
            Account dbAccount = filterAccountEntityByInstanceAndGitId(dbAccounts, instance, gitAccountEntity.getGitId());
            if (dbAccount == null) {
                // new account, fill git instance and updated at
                gitAccountEntity.setGitInstance(instance.name().toLowerCase());
                gitAccountEntity.setUpdatedAt(LocalDateTime.now());

                accountService.save(gitAccountEntity);
            } else {
                // update account, state, acct_fullname, and updated at
                dbAccount.setState(gitAccountEntity.getState());
                dbAccount.setAcctFullname(gitAccountEntity.getAcctFullname());

                dbAccount.setUpdatedAt(LocalDateTime.now());

                accountService.updateById(dbAccount);
            }
        }

        log.debug("Importing all accounts end");
    }

    /**
     * 获取所有用户
     *
     * @param instance 实例
     * @return 用户列表
     */
    private List<UserDTO> fetchAllAccounts(INSTANCE instance) {
        this.gitInstance = instance;
        Integer page = 1;
        Integer perPage = 100;
        List<UserDTO> accounts = new ArrayList<>();
        while (true) {
            log.debug("Fetched {} accounts", accounts.size());
            List<UserDTO> temp = gateway().getAllUsers(page, perPage);
            if (temp.isEmpty()) {
                break;
            } else if (temp.size() < perPage) {
                accounts.addAll(temp);
                break;
            } else {
                accounts.addAll(temp);
                page++;
            }
        }
        log.info("Fetched {} accounts", accounts.size());
        return accounts;
    }

    /**
     * 过滤出实例和GitLab ID相同的Account实体
     *
     * @param entities Account实体列表
     * @param instance 实例
     * @param gitId GitLab ID
     * @return 实例和GitLab ID相同的Account实体
     */
    Account filterAccountEntityByInstanceAndGitId(List<Account> entities, INSTANCE instance, Long gitId) {
        return entities
            .stream()
            .filter(item -> item.getGitInstance().equals(instance.name().toLowerCase()) && item.getGitId().equals(gitId))
            .findFirst()
            .orElse(null);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void importCommits(INSTANCE instance, String start, String end) {
        log.debug("Importing Commits in {} from {} to {} start", instance, start, end);
        this.gitInstance = instance;

        // 0. Save import log
        ImportLog importLog = registerImportLog(instance, start, end);

        try {
            // 1. Fetch commits
            List<CommitDTO> commits = fetchCommits(instance, start, end);

            // 2. Save commit logs
            List<CommitLog> commitLogEntities = buildCommitLog(instance, importLog.getId(), commits);
            commitLogService.saveBatch(commitLogEntities, 100);

            // 3. Update import log
            importLog.setImportCount(commitLogEntities.size());
            importLog.setStatus(2);
            importLogService.updateById(importLog);
        } catch (Exception e) {
            log.error("Failed to save commit logs", e);
            importLog.setStatus(3);
            importLogService.updateById(importLog);
            throw e;
        }

        log.debug("Importing Commits end");
    }

    /**
     * 注册导入工作记录
     *
     * @param instance 实例
     * @param start 开始日期 格式: 2025-01-01
     * @param end 结束日期 格式: 2025-01-01
     * @return 导入工作记录
     */
    private ImportLog registerImportLog(INSTANCE instance, String start, String end) {
        ImportLog importLog = new ImportLog();
        importLog.setGitInstance(instance.name().toLowerCase());
        importLog.setImportTime(LocalDateTime.now());
        importLog.setStartTime(LocalDateTime.parse(start + "T00:00:00"));
        importLog.setEndTime(LocalDateTime.parse(end + "T00:00:00"));
        importLog.setImportCount(0);
        importLog.setStatus(1);
        importLogService.save(importLog);
        return importLog;
    }

    /**
     * 获取指定日期范围内的提交记录
     *
     * <h4>算法：</h4>
     *
     * <ol>
     * <li>通过gitlab-api获取所有“活跃”的repo，活跃的时间未{@link GitImportServiceImpl#ACTIVE_FROM}</li>
     * <li>从gitlab获取所有的branch</li>
     * <li>依据repo和branch，从gitlab获取所有的commit，并过滤重复的commit，得到commit的pk</li>
     * <li>依据commit的pk，从gitlab获取commit的详细信息</li>
     * </ol>
     *
     * @param instance 实例
     * @param start 开始日期 格式: 2025-01-01
     * @param end 结束日期 格式: 2025-01-01
     * @return 提交记录列表
     */
    private List<CommitDTO> fetchCommits(INSTANCE instance, String start, String end) {
        // 1. Fetch all active repositories by gitlab api
        log.debug("Fetching all active repositories by gitlab api from {} to {}", start, end);
        List<RepoDTO> activeRepos = filterActiveRepos(getAllRepos(instance));

        // 2. Fetch all branches by gitlab api and fill to repo
        int branchSubCount = 0;
        int branchSum = 0;
        for (RepoDTO repo : activeRepos) {
            repo.setBranches(gateway().getBranches(repo.getId()));
            branchSum += repo.getBranches().size();
            branchSubCount++;
            if (branchSubCount % 20 == 0) {
                log.info("Fetched {} repos branches", branchSubCount);
            }
        }
        log.info("Fetched {} branches", branchSum);

        // 3. Fetch commits
        List<Repository> activeRepoParam = activeRepos.stream().map(repoConv::gitRepoDtoToEntity).collect(Collectors.toList());
        Set<CommitPkDTO> totalCommitPks = fetchNoRepeatCommitPks(activeRepoParam, start, end);
        log.debug("Fetched {} commits by {} repositrories and {} branches", totalCommitPks.size(), activeRepoParam.size(), branchSum);

        // 4. clean existing pk in database
        commitLogService
            .list(new LambdaQueryWrapper<CommitLog>().eq(CommitLog::getGitInstance, instance.name().toLowerCase()))
            .forEach(item -> {
                totalCommitPks.remove(new CommitPkDTO(item.getRepositoryId(), item.getSha()));
            });

        // 5. Fetch commit details
        log.debug("Fetching commit details from {} commits...", totalCommitPks.size());
        return fetchCommitsByPk(totalCommitPks, activeRepoParam);
    }

    /**
     * 获取指定日期范围内的提交记录的pk，并过滤重复的commit
     *
     * @param activeRepos 活跃的repo列表
     * @param start 开始日期 格式: 2025-01-01
     * @param end 结束日期 格式: 2025-01-01
     * @return 提交记录的pk列表
     */
    private Set<CommitPkDTO> fetchNoRepeatCommitPks(List<Repository> activeRepos, String start, String end) {
        Set<CommitPkDTO> totalCommitPks = new HashSet<>();
        Integer page = 1;
        Integer perPage = 100;
        for (Repository repo : activeRepos) {
            log.debug("Fetching commits by repository id {} with branches {}", repo.getId(), repo.getBranchIds());
            for (String branch : repo.getBranchIds().split(",")) {
                page = 0;
                Set<CommitPkDTO> commitPks = new HashSet<>();
                while (true) {
                    Set<CommitPkDTO> temp = gateway().getCommitsPk(repo.getGitId(), branch, start, end, page, perPage);
                    if (temp.isEmpty()) {
                        break;
                    } else if (temp.size() < perPage) {
                        commitPks.addAll(temp);
                        break;
                    } else {
                        commitPks.addAll(temp);
                        page++;
                    }
                }
                log.debug("Fetched {} commits of {}", commitPks.size(), branch);
                totalCommitPks.addAll(commitPks);
            }
        }
        return totalCommitPks;
    }

    /**
     * 依据commit的pk，从gitlab获取commit的详细信息, 并补充repo的web_url
     *
     * @param commitPks commit的pk列表
     * @param activeRepos 活跃的repo列表
     * @return 提交记录列表
     */
    private List<CommitDTO> fetchCommitsByPk(Set<CommitPkDTO> commitPks, List<Repository> activeRepos) {
        List<CommitDTO> commits = new ArrayList<>();
        int page = 0;
        for (CommitPkDTO commitPk : commitPks) {
            CommitDTO commit = gateway().getCommitByPk(commitPk);

            commit.setRepoId(commitPk.getRepoId());
            commit.setWebUrl(
                activeRepos
                    .stream()
                    .filter(item -> item.getGitId().equals(commitPk.getRepoId()))
                    .findFirst()
                    .get()
                    .getWebUrl() +
                "/commit/" +
                commitPk.getSha()
            );

            commits.add(commit);
            if (page % 20 == 0) {
                log.info("Fetched {} commit details", page);
            }
            page++;
        }
        log.debug("Fetched {} commit details", commits.size());
        return commits;
    }

    /**
     * 导入GitLab提交记录, 采用复合算法
     *
     * @param instance 实例
     * @param start 开始日期 格式: 2025-01-01
     * @param end 结束日期 格式: 2025-01-01
     */
    @Override
    public void importCommitsByCompose(INSTANCE instance, String start, String end) {
        log.debug("Importing Commits in {} from {} to {} start", instance, start, end);
        this.gitInstance = instance;

        // 0. Save import log
        ImportLog importLog = registerImportLog(instance, start, end);

        try {
            // 1. Fetch commits by compose algorithm
            List<CommitDTO> commits = fetchCommitsByCompose(instance, start, end);

            // 2. Save commit logs
            List<CommitLog> commitLogEntities = buildCommitLog(instance, importLog.getId(), commits);
            commitLogService.saveBatch(commitLogEntities, 100);

            // 3. Update import log
            importLog.setImportCount(commitLogEntities.size());
            importLog.setStatus(2);
            importLogService.updateById(importLog);
        } catch (Exception e) {
            log.error("Failed to save commit logs", e);
            importLog.setStatus(3);
            importLogService.updateById(importLog);
            throw e;
        }

        log.debug("Importing Commits end");
    }

    /**
     * 通过复合算法获取指定日期范围内的提交记录
     *
     * <h4>算法：</h4>
     *
     * <ol>
     * <li>通过db获取所有“活跃”的repo，活跃的时间未{@link GitImportServiceImpl#ACTIVE_FROM}</li>
     * <li>依据repo和branch，从gitlab获取所有的commit，并过滤重复的commit，得到commit的pk</li>
     * <li>依据commit的pk，从gitlab获取commit的详细信息</li>
     * </ol>
     *
     * @param instance 实例
     * @param start 开始日期 格式: 2025-01-01
     * @param end 结束日期 格式: 2025-01-01
     * @return 提交记录列表
     */
    private List<CommitDTO> fetchCommitsByCompose(INSTANCE instance, String start, String end) {
        // 1. Fetch all active repositories by database, lastActivityAt > ACTIVE_FROM
        log.debug("Fetching all active repositories by database from {} to {}, lastActivityAt > {}", start, end, ACTIVE_FROM);
        List<Repository> activeRepos = repoService
            .lambdaQuery()
            .gt(Repository::getLastActivityAt, LocalDateTime.ofInstant(ACTIVE_FROM, java.time.ZoneId.systemDefault()))
            .eq(Repository::getGitInstance, instance.name().toLowerCase())
            .list();

        // 2. Fetch commits
        Set<CommitPkDTO> totalCommitPks = fetchNoRepeatCommitPks(activeRepos, start, end);
        log.debug("Fetched {} commits by {} repositrories", totalCommitPks.size(), activeRepos.size());

        // 3. Fetch commit details
        log.debug("Fetching commit details from {} commits...", totalCommitPks.size());

        // 4. clean existing pk in database
        commitLogService
            .list(new LambdaQueryWrapper<CommitLog>().eq(CommitLog::getGitInstance, instance.name().toLowerCase()))
            .forEach(item -> {
                totalCommitPks.remove(new CommitPkDTO(item.getRepositoryId(), item.getSha()));
            });
        log.debug("Cleaned {} commits from database, these commits will be fetched again", totalCommitPks.size());

        return fetchCommitsByPk(totalCommitPks, activeRepos);
    }

    /**
     * 构建CommitLog实体
     *
     * @param instance 实例
     * @param commits 提交信息列表
     *
     * @return CommitLog实体列表
     */
    private List<CommitLog> buildCommitLog(INSTANCE instance, String importLogId, List<CommitDTO> commits) {
        return commits
            .stream()
            .map(item -> {
                CommitLog entity = commitConv.gitCommitDtoToEntity(item);

                // set git instance and updated at
                entity.setGitInstance(instance.name().toLowerCase());
                entity.setUpdatedAt(LocalDateTime.now());

                // set import log id
                entity.setImportLogId(importLogId);

                // set commit type
                if (item.getParentIds().length > 1) {
                    // merge commit
                    entity.setCommitType(2);
                } else if (item.getParentIds().length == 1) {
                    // normal commit
                    entity.setCommitType(1);
                } else {
                    // root commit
                    entity.setCommitType(0);
                }

                // set stats
                entity.setAdditions(item.getStats().getAdditions());
                entity.setDeletions(item.getStats().getDeletions());
                entity.setTotalChanges(item.getStats().getTotal());

                return entity;
            })
            .collect(Collectors.toList());
    }

    @Override
    public void importCommitsByMonth(INSTANCE instance, String month) {
        this.gitInstance = instance;
        LocalDate start = LocalDate.parse(month + "-01");
        LocalDate end = start.plusMonths(1);
        log.info("Importing commits in {} from {} 00:00:00 to {} 00:00:00", instance, start, end);
        importCommits(instance, start.toString(), end.toString());
    }

    @Override
    public void importCommitsByComposeByMonth(INSTANCE instance, String month) {
        this.gitInstance = instance;
        LocalDate start = LocalDate.parse(month + "-01");
        LocalDate end = start.plusMonths(1);
        log.info("Importing commits in {} from {} 00:00:00 to {} 00:00:00", instance, start, end);
        importCommitsByCompose(instance, start.toString(), end.toString());
    }
}
